using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace LogFileViewer
{
    class LogfileReader : LogfileAccess
    {
        public LogfileReader(Dictionary<MessageTypes, List<LogEntry>> data, string filename) : base(data,filename)
        {
        }

        /// <summary>
        ///     Reads a file and picks out entries of the specified type only.
        /// </summary>
        /// <returns>Hashtable</returns>
        public Dictionary<MessageTypes, List<LogEntry>> readFile()
        {
            return readTypeInFile(MessageTypes.UNDEFINED);
        }

        /// <summary>
        ///     Reads a file and picks out entries of the specified type only.
        /// </summary>
        /// <param name="type">Type (INFO, DEBUG, WARN, ERROR, FATAL), set type to null to get all types</param>
        /// <returns>Hashtable</returns>
        public Dictionary<MessageTypes, List<LogEntry>> readTypeInFile(MessageTypes type)
        {
            string changeMsgType = "";
            if (type != MessageTypes.UNDEFINED)
            {
                changeMsgType = "<" + type.ToString() + ">";   
            }

            this.startProcess("Reading " + changeMsgType + " log file...");

            if (this.data == null)
            {
                this.data = new Dictionary<MessageTypes, List<LogEntry>>(MessageType.Length());
            }

            string tempLine;
            StringBuilder entryTemp = new StringBuilder();

            // Check if current type has to be read
            if (this.data.ContainsKey(type))
            {
                // Already exists, return without changes
                return this.data;
            }

            // Fill list with entries of selected type only
            do
            {
                tempLine = this.streamReader.ReadLine();
                if (tempLine == null || (tempLine != null && tempLine.IndexOf(".") == 2 && tempLine.IndexOf(" ") == 10))
                {
                    // Entry was appended before, save first
                    if (entryTemp.Length != 0)
                    {
                        LogEntry entry = Utilities.getEntryObject(entryTemp.ToString());
                        if (entry == null)
                        {
                            MessageBox.Show("An error occured while reading active\ntype entries. Current entry is:\n" + entryTemp.ToString(), "Internal Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            break;
                        }

                        if ((type != MessageTypes.UNDEFINED && entry.Type == type) || type == MessageTypes.UNDEFINED)
                        {
                            // Collection of this type doesn't exist
                            if (!this.data.ContainsKey(entry.Type))
                            {
                                this.data.Add(entry.Type, new List<LogEntry>());
                            }
                            // The collection of this type is not set or empty
                            else if (this.data[entry.Type] == null || this.data[entry.Type].Count == 0)
                            {
                                this.data[entry.Type] = new List<LogEntry>();
                            }

                            // Check if current entry already exists is NOT PERFORMED HERE (takes too long).
                            this.data[entry.Type].Add(entry);
                        }
                    }
                    entryTemp = new StringBuilder();
                    entryTemp.AppendLine(tempLine);
                }
                else
                {
                    entryTemp.AppendLine(tempLine);
                }

                this.incrementProgressBar();
            } while (tempLine != null);

            // Reset stream to start position
            this.fileStream.Position = 0;

            this.stopProcess();

            return data;
        }

        /// <summary>
        ///     Reads a file and picks out the needed entries only.
        /// </summary>
        /// <param name="startOffsets">Startoffsets that will be searched in the file directly.</param>
        /// <returns>Hashtable</returns>
        public Dictionary<MessageTypes, List<LogEntry>> readRowsInFile(Dictionary<MessageTypes, long[]> indexes, MessageTypes type, int iteratorStart)
        {
            if (indexes == null)
            {
                Console.WriteLine("No line indexes given, this is essentially needed to perform active row reload operations.");
                return null;
            }
            this.startProcess("Reading next " + Properties.Settings.Default.IteratorMaxActiveRows + " entries...");

            Dictionary<MessageTypes, List<LogEntry>> data = new Dictionary<MessageTypes, List<LogEntry>>(MessageType.Length());

            string tempLine;
            List<LogEntry> list = new List<LogEntry>();

            // Sorting with the first entry offset of the current iteration
            long[] offsets = indexes[type];

            for (int i = 0; i < offsets.Length; i++)
            {
                long lineStart = offsets[iteratorStart];
                this.fileStream.Position = lineStart;
                StringBuilder cache = new StringBuilder();
                int found = -1;
                int size = 256;
                while (found == -1)
                {
                    byte[] bytes = new byte[size];
                    this.fileStream.Read(bytes, 0, size);
                    // Look for the next linebreak after search position
                    string s = Encoding.Default.GetString(bytes);
                    cache.Append(s);
                    found = s.IndexOf("\r\n");
                    if ((cache.Length + size) > fileStream.Length)
                    {
                        break;
                    }
                }

                if (found == -1)
                {
                    tempLine = null;
                }
                else
                {
                    found += cache.ToString().Length - size;
                    tempLine = cache.ToString().Substring(0, found);
                }

                this.incrementProgressBar();

                if (tempLine == null)
                {
                    continue;
                }

                // Find out what message type
                if (tempLine.IndexOf(".") == 2 && tempLine.IndexOf(" ") == 10)
                {
                    LogEntry entry = Utilities.getEntryObject(tempLine);
                    list.Add(entry);
                }

                // End of file is reached
                if (found == -1 && fileStream.Length < (fileStream.Position + size))
                {
                    break;
                }
            }

            data[type] = list;

            this.stopProcess();

            return data;
        }

        /// <summary>
        ///     Reads a file and picks out the needed entry only.
        /// </summary>
        /// <param name="startOffsets">Startoffsets that will be searched in the file directly.</param>
        /// <returns>Hashtable</returns>
        public LogEntry readSingleEntry(long lineIndex)
        {
            this.startProcess("Reading next entry...");

            if (lineIndex >= fileStream.Length)
            {
                Console.WriteLine("Out of range with line index.");
                return null;
            }

            Regex regex = new Regex(RegExProvider.LOG_STRUCTURE_01, RegexOptions.Multiline);

            this.fileStream.Position = lineIndex;
            StringBuilder cache = new StringBuilder();
            bool found = false;
            int size = 256;

            MatchCollection matches = null;
            while (!found)
            {
                byte[] bytes = new byte[size];
                this.fileStream.Read(bytes, 0, size);

                // Look for the next linebreak after search position
                string s = Encoding.Default.GetString(bytes);
                cache.Append(s);

                matches = regex.Matches(cache.ToString());

                // When the next log entry starts, this one is finished
                if (matches != null && matches.Count > 0)
                {
                    found = true;
                }
                // When end of file is reached this one is finished too
                //else
                //{
                //    Regex regexend = new Regex(RegExProvider.LOG_STRUCTURE_02, RegexOptions.Multiline);
                //    matches = regexend.Matches(cache.ToString());
                //    if (matches != null && matches.Count > 0)
                //    {
                //        found = true;
                //    }
                //}

                if ((cache.Length + size) > fileStream.Length)
                {
                    break;
                }
            }

            this.incrementProgressBar();

            if (matches == null || matches.Count == 0)
            {
                // Specified entry not found at position
                return null;
            }

            // Create a log entry object
            LogEntry entry = Utilities.getEntryObject(matches);

            this.stopProcess();

            return entry;
        }
    }
}
